//==================================================================
//  Copyright (C) 2006-2007  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
/// @file data_schema.cpp
///
///
/// @author Davide Pasca
///
//==================================================================

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include "data_schema.h"

//===================================================================
static int isblanko( TCHAR c )
{
	return (c == _TXCHAR(' ') || c == _TXCHAR('\t') || c == _TXCHAR('\f') || c == _TXCHAR('\r') || c == _TXCHAR('\n'));
}

//===================================================================
static int iseol( TCHAR c )
{
	return (c == _TXCHAR('\f') || c == _TXCHAR('\r') || c == _TXCHAR('\n'));
}

//===================================================================
// not safe
static void get_name_and_value( TCHAR *line, TCHAR *name, TCHAR *value )
{
	int		strsize;

	name[0] = value[0] = 0;

	strsize = 0;
	for (; *line && isblanko( *line ); ++line);

	if ( *line == '"' )	// in case of a string defined by a quotation (has spaces inside)
	{	// proceed to look for the closing quotation
		++line;
		for (; *line && *line != _TXCHAR('"'); ++line)
			*name++ = *line;
		++line;
	}
	else
	{	// otherwise proceed to look for the whitespace
		for (; *line && !isblanko( *line ); ++line)
			*name++ = *line;
	}

	*name = 0;

	for (; *line && isblanko( *line ); ++line);

	for (; *line && !iseol( *line ); ++line)
		*value++ = *line;

	*value = 0;
}

//==================================================================
TCHAR *ItemBase::strcpyalloc( const TCHAR *srcp )
{
	TCHAR	*d;

	int	len = _tcslen( srcp );
	d = new	TCHAR [ len + 1 ];
	_tcscpy_s( d, len+1, srcp );
	return d;
}

//==================================================================
bool config_parse_next( FILE *fp, TCHAR *namep, TCHAR *valuep )
{
	TCHAR	*retstr;
	TCHAR	buff[4096];

	while ( 1 )
	{
		retstr = PSYS::TFGets_UTF8( fp, buff, _countof(buff) );
		if NOT( retstr )
		{
			if ( feof(fp) )
			{
				return true;
			}
			else
			{
				//throw "cfg file error";
				return true;
			}
		}
		else
		if ( buff[0] != _TXCHAR('#') )
		{
			// not safe
			get_name_and_value( buff, namep, valuep );
			return false;
		}
	}

	return false;
}

//==================================================================
static int get_nibble( TCHAR ch )
{
	if ( ch >= _TXCHAR('0') && ch <= _TXCHAR('9') )
		return (int)ch - _TXCHAR('0');
	
	if ( ch >= _TXCHAR('a') && ch <= _TXCHAR('f') )
		return (int)ch - _TXCHAR('a') + 10;

	if ( ch >= _TXCHAR('A') && ch <= _TXCHAR('F') )
		return (int)ch - _TXCHAR('A') + 10;

	PASSERT( 0 );
	return 255;
}

//==================================================================
static u_char get_hexbyte( const TCHAR *strp )
{
	return (get_nibble( strp[0] ) << 4) |
		   (get_nibble( strp[1] ) << 0);
}

//==================================================================
static PError SHA1_from_hexstring( u_char sha1hash[20], const TCHAR *strp )
{
	int	len = _tcslen( strp );
	if ( len != 20*2 )
		return PERROR;

	for (int i=0; i < 20; ++i)
	{
		sha1hash[i] = get_hexbyte( &strp[i*2] );
	}

	return POK;
}

//==================================================================
static void hexstring_from_SHA1( TCHAR *strp, int outstr_maxlen, const u_char sha1hash[20] )
{
	for (int i=0; i < 20; ++i)
	{
		_stprintf_s( strp+i*2, outstr_maxlen-i*2, _T("%02x"), sha1hash[i] );
	}
	strp[20*2] = 0;
}

//==================================================================
void ItemSHA1Hash::LoadFromString( const TCHAR *strp )
{
	SHA1_from_hexstring( (u_char *)_datap, strp );
}

//==================================================================
const TCHAR *ItemSHA1Hash::StoreToString( TCHAR *outstrp, int outstr_maxlen )
{
	hexstring_from_SHA1( outstrp, outstr_maxlen, (const u_char *)_datap );
	return outstrp;
}

//==================================================================
DataSchema::DataSchema( const TCHAR *stridp ) :
	_stridp(stridp)
{
}

//==================================================================
DataSchema::~DataSchema()
{
}

//==================================================================
void DataSchema::AddString( const TCHAR *stridp, TCHAR *datap, int maxstrlen )
{
	_itemsp_list.push_back( new ItemString( stridp, datap, maxstrlen ) );
}
//==================================================================
void DataSchema::AddSHA1Hash( const TCHAR *stridp, sha1_t *sha1hashp )
{
	_itemsp_list.push_back( new ItemSHA1Hash( stridp, sha1hashp ) );
}
//---------------------------------------------------------------------------
void DataSchema::AddInt( const TCHAR *stridp, int *datap, int i_min, int i_max )
{
	_itemsp_list.push_back( new ItemInt( stridp, datap, i_min, i_max ) );
}
//---------------------------------------------------------------------------
void DataSchema::AddULong( const TCHAR *stridp, u_long *datap, u_long ul_min, u_long ul_max )
{
	_itemsp_list.push_back( new ItemULong( stridp, datap, ul_min, ul_max ) );
}
//---------------------------------------------------------------------------
void DataSchema::AddFloat( const TCHAR *stridp, float *datap, float f_min, float f_max )
{
	_itemsp_list.push_back( new ItemFloat( stridp, datap, f_min, f_max ) );
}
//---------------------------------------------------------------------------
void DataSchema::AddBool( const TCHAR *stridp, bool *datap, const TCHAR *strid_alternativep )
{
	_itemsp_list.push_back( new ItemBool( stridp, datap, strid_alternativep ) );
}

//==================================================================
static int make_blank_safe_string( TCHAR *desp, const TCHAR *srcp, int dmax )
{
bool	has_blanks;

	has_blanks = false;
	for (int i=0; i < dmax-3; ++i)
	{
		// we copy just in case it doesn't have blanks 8)
		if NOT( desp[i] = srcp[i] )
		{
			if ( has_blanks )
			{
				*desp++ = _TXCHAR('"');
				for (int j=0; j < i; ++j)
					*desp++ = *srcp++;
				*desp++ = _TXCHAR('"');
				*desp = 0;
			}
			return 0;
		}

		if ( isblanko( srcp[i] ) )
		{
			has_blanks = true;
			// don't break keep going, we need to know the size
		}
	}

	return -1;
}

//==================================================================
PError DataSchema::SaveData( FILE *fp ) const
{
	if ( PSYS::FTPrintF_S_UTF8( fp, _T("@BEGIN %s\n"), _stridp ) < 0 )
		return PERROR;

	for (int i=0; i < _itemsp_list.size(); ++i)
	{
	ItemBase	*itemp = _itemsp_list[i];
	TCHAR		buff[256];
	TCHAR		buff2[1024];
	
		int err = make_blank_safe_string( buff, itemp->_stridp, _countof(buff) );
		PASSERT( err == 0 );
		if ( err )
			return PERROR;

		itemp->StoreToString( buff2, _countof(buff2) );
		if ( PSYS::FTPrintF_S_UTF8( fp, _T("%s\t\t%s\n"), buff, buff2 ) < 0 )
			return PERROR;
	}

	if ( PSYS::FTPrintF_S_UTF8( fp, _T("@END %s\n"), _stridp ) < 0 )
		return PERROR;

	return POK;
}

//==================================================================
ItemBase *DataSchema::item_find_by_strid( const TCHAR *stridp )
{
	for (int i=0; i < _itemsp_list.size(); ++i)
	{
		if ( !_tcsicmp( stridp, _itemsp_list[i]->_stridp ) )
			return _itemsp_list[i];

		if ( _itemsp_list[i]->_strid_alternativep )
			if ( !_tcsicmp( stridp, _itemsp_list[i]->_strid_alternativep ) )
				return _itemsp_list[i];
	}

	return NULL;
}

//==================================================================
int DataSchema::LoadData( FILE *fp )
{
ItemBase	*itemp;

	TCHAR	name[256];
	TCHAR	value[1024];

	while NOT( config_parse_next( fp, name, value ) )
	{
		if ( value[0] )
		{
			if ( !_tcsicmp( name, _T("@begin") ) )
				continue;

			if ( !_tcsicmp( name, _T("@end") ) )
				return 0;

			if NOT( itemp = item_find_by_strid( name ) )
			{
				PASSERT( 0 );
			}
			else
			{
				itemp->LoadFromString( value );
			}
		}
	}

	return -1;
}

//==================================================================
int	DataSchema::LoadSchemas_s( FILE *fp, LoaderItem *loader_itemsp, int n_loader_items )
{
	ItemBase	*itemp;

	TCHAR		name[256];
	TCHAR		value[1024];

	LoaderItem	*cur_loader_itemp = NULL;
	DataSchema	*cur_schemap = NULL;

	while NOT( config_parse_next( fp, name, value ) )
	{
		if ( value[0] )
		{
			if ( !_tcsicmp( name, _T("@begin") ) )
			{
				for (int i=0; i < n_loader_items; ++i)
				{
					if ( !_tcsicmp( loader_itemsp[i]._namep, value ) )
					{
						cur_loader_itemp = &loader_itemsp[i];
						cur_schemap = cur_loader_itemp->_Callback( fp, cur_loader_itemp->_userdatap );
						break;
					}
				}
				PASSERT( cur_loader_itemp != NULL );
				PASSERT( cur_schemap != NULL );
				continue;
			}
			else
			if ( !_tcsicmp( name, _T("@end") ) )
			{
				cur_loader_itemp = NULL;
				cur_schemap = NULL;
			}
			else
			{
				if NOT( itemp = cur_schemap->item_find_by_strid( name ) )
				{
					PASSERT( 0 );
				}
				else
				{
					itemp->LoadFromString( value );
				}
			}
		}
	}

	return 0;//-1;
}
